package com.wgo.precise.client.ui.model;

import static com.wgo.precise.client.ui.controller.PrecisePluginImage.IMG_REQUIREMENT;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.views.properties.IPropertySource;

import com.wgo.precise.client.ui.controller.Assert;
import com.wgo.precise.client.ui.controller.ClientSession;
import com.wgo.precise.client.ui.controller.ModelStatus;
import com.wgo.precise.client.ui.controller.RequirementPlugin;
import com.wgo.precise.client.ui.model.facade.IMenuItemsProvider;
import com.wgo.precise.client.ui.view.action.GetPdfAction;
import com.wgo.precise.client.ui.view.util.RequirementPropertySource;
import com.wgo.precise.client.ui.view.util.ViewerAction;
import com.wgo.precise.common.VO.Category;
import com.wgo.precise.common.VO.Nameable;
import com.wgo.precise.common.VO.Persistent;
import com.wgo.precise.common.VO.Requirement;
import com.wgo.precise.common.VO.Stakeholder;

/**
 * 
 * @author petterei
 *
 * @version $Id: RequirementWrapper.java,v 1.6 2006-02-19 16:29:37 petterei Exp $
 */
public class RequirementWrapper extends NodeChild<CategoryWrapper, Requirement> implements IMenuItemsProvider{

	private static final Class<Category> PARENT_INTERFACE = Category.class;

	private static String PARENT_CORE_METHOD_NAME = "Category";
    private static Map<Long,ClientPropertyDefinition<Persistent>> requirementSpesifications = new HashMap<Long,ClientPropertyDefinition<Persistent>>();
    private static List<ClientPropertyDefinition<Persistent>> sortedRequirementSpesifications = new ArrayList<ClientPropertyDefinition<Persistent>>();

    private Requirement requirement;
    
    static {
//        PropertySpecification<Persistent> id;
//        id = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "ID", PropertyType.INTEGER) {
//			@Override
//			public Object getValue(Persistent persistent) {
//				return ((Requirement)persistent).getBusinessId();
//			}
//			@Deprecated // Read-only value, thus should not be used
//			@SuppressWarnings("unused") // Read-only value, thus parameters not used
//			@Override
//			public void setValue(Persistent persistent, Object value) {
//				// read-only value, thus the persistent item should not be touched
//			}
//        };
//        id.setDescription("The requirement ID.");
//        id.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        id.setDefaultValue("0");
//        id.setReadOnly(true);
//        addRequirementSpesification(id);
        
        EnumPropertySpecification<Persistent> priority;
        priority = new EnumPropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Priority", PropertyType.ENUMERATION){
			@Override
			public void setValue(Persistent persistent, Object value) {
				((Requirement)persistent).setPriority((Integer)value);
			}
			@Override
			protected Object getValue(Persistent persistent) {
				return ((Requirement)persistent).getPriority();
			}
        };
        priority.setDescription("The requirement priority.");
        priority.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
        priority.addEnumerationElement("Low");
        priority.addEnumerationElement("Medium");
        priority.addEnumerationElement("High");
        priority.setDefaultEnumerationElement(1);
        addRequirementSpesification(priority);

//        EnumPropertySpecification<Persistent> status;
//        status = new EnumPropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Status", PropertyType.ENUMERATION){
//			@Override
//			public void setValue(Persistent persistent, Object value) {
//				((Requirement)persistent).setStatus((Integer)value);
//			}
//			@Override
//			protected Object getValue(Persistent persistent) {
//				return ((Requirement)persistent).getStatus();
//			}
//        };
//        status.setDescription("The requirement status.");
//        status.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        status.addEnumerationElement("Open");
//        status.addEnumerationElement("Approved");
//        status.addEnumerationElement("Assigned");
//        status.addEnumerationElement("Closed");
//        status.addEnumerationElement("Stopped");
//        status.addEnumerationElement("Merged");
//        status.addEnumerationElement("Reopened");
//        status.setDefaultEnumerationElement(0);
//        addRequirementSpesification(status);

//        PropertySpecification<Persistent> version;
//        version = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Version", PropertyType.STRING) {
//			@Override
//			protected Object getValue(Persistent persistent) {
//				return ((Requirement)persistent).getVersion();
//			}
//			@Deprecated // Read-only value, thus should not be used
//			@SuppressWarnings("unused") // Read-only value, thus parameters not used
//			@Override
//			protected void setValue(Persistent item, Object value) {
//				// read-only value, thus the persistent item should not be touched
//			}
//        };
//        version.setDescription("The requirement version.");
//        version.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        version.setDefaultValue("0.0");
//        version.setReadOnly(true);
//        addRequirementSpesification(version);

//        PropertySpecification<Persistent> submittedDate;
//        submittedDate = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Date submitted", PropertyType.DATE){
//			@Override
//			protected Object getValue(Persistent persistent) {
//				return ((Requirement)persistent).getSubmittedDate();
//			}
//			@Deprecated // Read-only value, thus should not be used
//			@SuppressWarnings("unused") // Read-only value, thus parameters not used
//			@Override
//			protected void setValue(Persistent item, Object value) {
//				// read-only value, thus the persistent item should not be touched
//			}
//        };
//        submittedDate.setDescription("The requirement submissiondate.");
//        submittedDate.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        submittedDate.setDefaultValue("0000.00.00 00:00");
//        submittedDate.setReadOnly(true);
//        addRequirementSpesification(submittedDate);

//        PropertySpecification<Persistent> lastUpdatedDate;
//        lastUpdatedDate = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Last update", PropertyType.DATE) {
//			@Override
//			protected Object getValue(Persistent persistent) {
//				return ((Requirement)persistent).getLastUpdatedDate();
//			}
//			@Deprecated // Read-only value, thus should not be used
//			@SuppressWarnings("unused") // Read-only value, thus parameters not used
//			@Override
//			protected void setValue(Persistent item, Object value) {
//				// read-only value, thus the persistent item should not be touched
//			}
//        };
//        lastUpdatedDate.setDescription("Date and time of last update of the requirement.");
//        lastUpdatedDate.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        lastUpdatedDate.setDefaultValue("0000.00.00 00:00");
//        lastUpdatedDate.setReadOnly(true);
//        addRequirementSpesification(lastUpdatedDate);

//        PropertySpecification<Persistent> reporterOrOwner;
//        reporterOrOwner = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Reporter/Owner", PropertyType.STAKEHOLDER){
//			@Override
//			public void setValue(Persistent persistent, Object value) {
//				Stakeholder stakeholder = null;
//				if (value instanceof StakeholderWrapper) {
////					if (null != value) { //Redundant null check: The variable value cannot be null at this location
//						stakeholder = ((StakeholderWrapper)value).getDomainModelObject();
////					}
//				} else if (null != value){
//					RequirementPlugin.log(new ModelStatus(IStatus.WARNING, "Invalid value-type (" + value.getClass().getSimpleName() + ") for field reporterOrOwner. Should be " + PropertyType.STAKEHOLDER.getClazz().getSimpleName()));
//				}
//				((Requirement)persistent).setReporterOrOwner(stakeholder);
//				RequirementPlugin.getInstance().getPreciseSubject().notifyModelChanged(null); //FIXME
//			}
//			@Override
//			protected Object getValue(Persistent persistent) {
//				StakeholderWrapper wrapper = null;
//				Stakeholder author = ((Requirement)persistent).getReporterOrOwner();
//				if (null != author) {
//					wrapper = RequirementPlugin.getInstance().getSession().getWrapperFactory().getWrapper(author);
//					Assert.warningIfNull(wrapper, "Did not retrieve "+ StakeholderWrapper.class.getSimpleName() +" for " + author.getName() + " [" +author.getDbId() + "]");
//				}
//				return wrapper;
//			}
//        };
//        reporterOrOwner.setReadOnly(false);
//        reporterOrOwner.setDescription("The reporter or owner of the requirement.");
//        reporterOrOwner.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        reporterOrOwner.setDefaultValue(null);
//        addRequirementSpesification(reporterOrOwner);

//        PropertySpecification<Persistent> assignedTo;
//        assignedTo = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Assigned to", PropertyType.STAKEHOLDER){
//			@Override
//			public void setValue(Persistent persistent, Object value) {
////				Requirement thisRequirement = (Requirement)persistent;
//				Stakeholder stakeholder = null;
//				if (value instanceof StakeholderWrapper) {
////					if (null != value) { //Redundant null check: The variable value cannot be null at this location
//						stakeholder = ((StakeholderWrapper)value).getDomainModelObject();
////					}
//				} else if (null != value){
//					RequirementPlugin.log(new ModelStatus(IStatus.WARNING, "Invalid value-type (" + value.getClass().getSimpleName() + ") for field assignedTo. Should be " + PropertyType.STAKEHOLDER.getClazz().getSimpleName()));
//				}
////				Stakeholder currentAssignedTo = thisRequirement.getAssignedTo();
////				if (currentAssignedTo != null && currentAssignedTo != stakeholder) {
////					thisRequirement.setAssignedTo(stakeholder);
////					((Requirement)persistent).setAssignedTo(stakeholder);
////					if (null != currentAssignedTo) {
////						StakeholderWrapper currentWrapper = RequirementPlugin.getInstance().getSession().getStakeholderWrapper(currentAssignedTo);
////						RequirementPlugin.getInstance().notifyModelChanged(currentWrapper);
////						if (null != stakeholder) {
////							RequirementPlugin.getInstance().notifyModelChanged((StakeholderWrapper)value);
////						}
////					} else {
////						RequirementPlugin.getInstance().notifyModelChanged((StakeholderWrapper)value);
////					}
////				}
//				((Requirement)persistent).setAssignedTo(stakeholder);
//				RequirementPlugin.getInstance().getPreciseSubject().notifyModelChanged(null); //FIXME
//			}
//			@Override
//			protected Object getValue(Persistent persistent) {
//				StakeholderWrapper wrapper = null;
//				Stakeholder author = ((Requirement)persistent).getAssignedTo();
//				if (null != author) {
//					wrapper = RequirementPlugin.getInstance().getSession().getWrapperFactory().getWrapper(author);
//					Assert.warningIfNull(wrapper, "Did not retrieve "+ StakeholderWrapper.class.getSimpleName() +" for " + author.getName() + " [" +author.getDbId() + "]");
//				}
//				return wrapper;
//			}
//        };
//        assignedTo.setReadOnly(false);
//        assignedTo.setDescription("Whom the requirement is assigned to.");
//        assignedTo.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
//        assignedTo.setDefaultValue("None");
//        addRequirementSpesification(assignedTo);

        PropertySpecification<Persistent> stakeholdersDefinition;
        stakeholdersDefinition = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Stakeholders", PropertyType.STAKEHOLDER_COLLECTION){
			@SuppressWarnings("unchecked")
			@Override
			public void setValue(Persistent persistent, Object value) {
				if (null != value && value instanceof Collection) {
					Requirement requirement = (Requirement)persistent;
					Collection<StakeholderWrapper> wrappers = (Collection<StakeholderWrapper>)value;
					Collection<Stakeholder> stakeholders = requirement.getStakeholders();
					Collection<Stakeholder> stakeholdersToBeRemoved = new HashSet<Stakeholder>();
					stakeholdersToBeRemoved.addAll(stakeholders);
					for (StakeholderWrapper wrapper : wrappers){
						Stakeholder stakeholder = wrapper.getDomainModelObject();
						if (stakeholders.contains(stakeholder)) {
							stakeholdersToBeRemoved.remove(stakeholder);
						} else {
							requirement.addStakeholder(stakeholder);
						}
					}
					for (Stakeholder stakeholder : stakeholdersToBeRemoved) {
						requirement.removeStakeholder(stakeholder);
					}
				} else {
					((Requirement)persistent).getStakeholders().clear();
				}
				RequirementPlugin.getInstance().getPreciseSubject().notifyModelChanged(null); //FIXME
			}
			@Override
			protected Object getValue(Persistent persistent) {
				List<Stakeholder> stakeholders = ((Requirement)persistent).getStakeholders();
				Collection<IAdaptable> wrappers = new ArrayList<IAdaptable>(stakeholders.size());
				for (Stakeholder stakeholder : stakeholders) {
					wrappers.add(RequirementPlugin.getInstance().getSession().getWrapperFactory().getWrapper(stakeholder));
				}
				return wrappers;
//				return "Number of Stakeholders: " + ((Requirement)persistent).getStakeholders().size();
			}
        };
        stakeholdersDefinition.setReadOnly(false);
        stakeholdersDefinition.setDescription("The requirement stakeholders.");
        stakeholdersDefinition.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
        stakeholdersDefinition.setDefaultValue(null);
        addRequirementSpesification(stakeholdersDefinition);

        PropertySpecification<Persistent> linkedRequirements;
        linkedRequirements = new PropertySpecification<Persistent>(Configuration.getInstance().getNextRequirementDefinitionId(), "Linked requirements", PropertyType.REQUIREMENT_COLLECTION){
			@SuppressWarnings("unchecked")
			@Override
			public void setValue(Persistent persistent, Object value) {
				if (null != value && value instanceof Collection) {
					Requirement requirement = (Requirement)persistent;
					Collection<RequirementWrapper> wrappers = (Collection<RequirementWrapper>)value;
					Collection<Requirement> linkedToRequirements = requirement.getLinkedRequirements();
					Collection<Requirement> linkedRequirementsToBeRemoved = new HashSet<Requirement>();
					linkedRequirementsToBeRemoved.addAll(linkedToRequirements);
					for (RequirementWrapper wrapper : wrappers){
						Requirement stakeholder = wrapper.getDomainModelObject();
						if (linkedToRequirements.contains(stakeholder)) {
							linkedRequirementsToBeRemoved.remove(stakeholder);
						} else {
							requirement.addLinkedRequirement(stakeholder);
						}
					}
					for (Requirement stakeholder : linkedRequirementsToBeRemoved) {
						requirement.removeLinkedRequirement(stakeholder);
					}
				} else {
					((Requirement)persistent).getStakeholders().clear();
				}
				RequirementPlugin.getInstance().getPreciseSubject().notifyModelChanged(null); //FIXME
			}
			@Override
			protected Object getValue(Persistent persistent) {
				List<Requirement> requirements = ((Requirement)persistent).getLinkedRequirements();
				List<IAdaptable> wrappers = null;
				if (0 < requirements.size()) {
					wrappers = new ArrayList<IAdaptable>(requirements.size());
					ClientSession session = RequirementPlugin.getInstance().getSession();
					for (Requirement requirement : requirements) {
						RequirementWrapper wrapper = session.getWrapperFactory().getWrapper(requirement);
						if (null != wrapper) {
							wrappers.add(wrapper);
						}
					}
				}
				return wrappers;
			}
        };
        linkedRequirements.setReadOnly(false);
        linkedRequirements.setDescription("The linked requirements.");
        linkedRequirements.setPosition(Configuration.getInstance().getNextRequirementDefinitionPosition());
        linkedRequirements.setDefaultValue("None");
        addRequirementSpesification(linkedRequirements);
    }
    public static Map<Long,ClientPropertyDefinition<Persistent>> getRequirementSpesifications() {
        return requirementSpesifications;
    }
    public static List<ClientPropertyDefinition<Persistent>> getSortedRequirementSpesifications() {
        return sortedRequirementSpesifications;
    }
    private static synchronized void addRequirementSpesification (ClientPropertyDefinition<Persistent> propertySpesification) {
        requirementSpesifications.put(propertySpesification.getIdentifier(), propertySpesification);
        sortedRequirementSpesifications.add(propertySpesification);
    }

    protected RequirementWrapper(Requirement requirement) {
		super(requirement, PARENT_INTERFACE, PARENT_CORE_METHOD_NAME);
		init(requirement);
	}

    protected RequirementWrapper(Requirement requirement, CategoryWrapper wrapper) {
        super(requirement, wrapper, PARENT_INTERFACE, PARENT_CORE_METHOD_NAME);
    }
	
    /**
	 * @see com.wgo.precise.client.ui.model.NodeChild#init(remato.common.domain.helpers.Nameable)
	 */
	@Override
	protected void init(Nameable nameable) {
        this.requirement = (Requirement)nameable;
        initPropertiesFromDomainObject(getSortedRequirementSpesifications());
        
	}

	/**
     * Ovverided to make public.
     * 
     * @see com.wgo.precise.client.ui.model.NodeChild#getUserDefinedTypes()
     */
    @Override
    public List<AbstractPropertySpesification> getUserDefinedTypes() {
        return super.getUserDefinedTypes();
    }

	public void verifyProperties() {
		for (PropertySetWrapper setWrapper : getPropertyDefinitionSets()) {
			for (AbstractPropertySpesification wrapper : setWrapper.getPopertyDefinitions()) {
				verifyProperty((PropertyDefWrapper)wrapper);
			}
		}
	}

	public void verifyProperty(PropertyDefWrapper definition){
		if (null != definition) {
			com.wgo.precise.common.VO.PropertyValue value = requirement.getPropertyValue(definition.getDomainModelDefinition());
			if (null == value) {
				RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass().getSimpleName() + ": Tried get property value for " + definition.getSimpleName() + ", but no value for requirement XXX"));
				
//				value = RequirementPlugin.getInstance().getSession().getDomainSession().<remato.domain.common.PropertyValue>createConcept(remato.domain.common.PropertyValue.class, definition.getDomainModelDefinition());
//				requirement.putPropertyValue(value);
			}
			PropertyValueWrapper valueWrapper = new PropertyValueWrapper(value, definition);
			setPropertyValue(valueWrapper);
		} else {
			RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass().getSimpleName() + ": Tried to definitions value, when definition was null."));
		}
	}
	
    public Collection<PropertySetWrapper> getPropertyDefinitionSets() {
    	Collection<PropertySetWrapper> result = null;
    	if (null != getParent()) {
//    		result = ((CategoryWrapper)getParent()).getAllPropertyDefinitionSetsForRequirements();
    		result = this.getParent().getPropertyDefinitionSets();
    	} else {
    		RequirementPlugin.log(new ModelStatus(IStatus.WARNING, getClass().getSimpleName() + " had no parent set."));
    	}
    	return result;
    }

    @Override
    public Object getAdapter(Class adapter) {
        if (adapter == IPropertySource.class) {
            return new RequirementPropertySource(this);
        }
        return super.getAdapter(adapter);
    }

    /**
     * @see com.wgo.precise.client.ui.model.ClientModelItem#getSortingOrder()
     */
    public int getSortingOrder() {
        return Configuration.REQUIREMENT_SORT_ORDER;
    }
//    /**
//     * TODO: move to NodeChild
//     * 
//     * @see com.wgo.precise.client.ui.model.ClientModelItem#getCurrentPropertyValue(java.lang.Object)
//     */
//    @Override
//    public PropertyValue getCurrentPropertyValue(AbstractPropertySpesification id) {
//        PropertyValue value = super.getCurrentPropertyValue(id);
//        if (value == null) {
////            for (AbstractPropertySpesification spec : super.getUserDefinedTypes()) {
////                if (spec.getIdentifier() == id) {
////                    return new PropertyValue(null, spec);
////                }
////            }
//        	verifyProperties();
//        	value = super.getCurrentPropertyValue(id);
//        	if (value == null) {
//        		throw new RematoClientException("Client out of sync(Did not find PropertyValue with SpecificationID: "+ id +"), please save changes or refresh from server.");
//        	}
//        }
//        return value;
//    }
    
    

	@Override
	public List<ViewerAction> getMenuItemActions() {
        List<ViewerAction> actionList= new ArrayList<ViewerAction>();
//        actionList.add(new CreatePdfAction(RequirementPlugin.getInstance().getActiveShell().getShell(),(Requirement)this.getDomainModelObject()));
        actionList.add(new GetPdfAction(RequirementPlugin.getInstance().getActiveShell().getShell(), this));
        actionList.addAll(super.getMenuItemActions());
        return actionList;
	}
	@Override
	public Image getImage() {
		return IMG_REQUIREMENT.getImage();
	}
}
